package mirrormonkey.util.annotations.parsing.memberlevel;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

import junit.framework.Assert;
import mirrormonkey.util.annotations.control.AnnotationOverride;
import mirrormonkey.util.annotations.control.IRClass;
import mirrormonkey.util.annotations.parsing.AnnotationParser;
import mirrormonkey.util.annotations.parsing.ClassIR;
import mirrormonkey.util.annotations.parsing.MemberIR;
import mirrormonkey.util.annotations.parsing.MethodIR;

import org.junit.BeforeClass;
import org.junit.Test;

/**
 * Contains test cases concerning <tt>MethodIR</tt>, which is a simple IR class
 * to not only collect annotations of methods, but also the methods themselves.
 * 
 * Please note that as <tt>MethodIR</tt> is considered an extension class, these
 * test cases only cover the extended functionality. It is assumed that the
 * basic test cases check out fine.
 * 
 * @author Philipp Christian Loewner
 * 
 */
public class MethodIRTest {

	public static AnnotationParser parser;

	@BeforeClass
	public static void setUpBeforeClass() {
		parser = new AnnotationParser();
	}

	public static class ValidityTestContainer {

		@IRClass(MethodIR.class)
		public void defaultIR() {
		}

	}

	public static class ExtendValidityTestContainer extends
			ValidityTestContainer {
		@Override
		@IRClass(MethodIR.class)
		public void defaultIR() {
		}

		@IRClass(MethodIR.class)
		public void direct() {
		}
	}

	/**
	 * Tests if every INTERNAL IR can be substituted with a <tt>MethodIR</tt>.
	 */
	@Test
	public void testValidity() {
		ClassIR ir = parser.parseClass(ExtendValidityTestContainer.class, null);
		Assert.assertEquals(2, ir.memberIRs.get(Method.class).size());
		for (MemberIR i : ir.memberIRs.get(Method.class)) {
			Assert.assertEquals(MethodIR.class, i.getClass());
		}
		for (Class<?> i : new Class<?>[] { Constructor.class, Field.class }) {
			Assert.assertTrue(ir.memberIRs.get(i).isEmpty());
		}
	}

	public static class InvalidClass {

		@IRClass(MethodIR.class)
		public Object field;

	}

	/**
	 * Tests if using <tt>MethodIR</tt> for a field will provoke an exception.
	 */
	@Test(expected = RuntimeException.class)
	public void testRejectField() {
		parser.parseClass(InvalidClass.class, null);
	}

	public static class DeclareSingleMethod {

		@IRClass(MethodIR.class)
		public void method() {
		}

	}

	public void checkMethod(ClassIR ir, Class<?> methodOwner, String methodName) {
		try {
			Assert.assertEquals(1, ir.memberIRs.get(Method.class).size());
			Method m = methodOwner.getMethod(methodName);
			Assert.assertEquals(m, ((MethodIR) ir.memberIRs.get(Method.class)
					.iterator().next()).getCollectedMethod());
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * Tests if methods are collected.
	 */
	@Test
	public void testMethodCollection() {
		ClassIR ir = parser.parseClass(DeclareSingleMethod.class, null);
		checkMethod(ir, DeclareSingleMethod.class, "method");
	}

	public static class ExtendDeclareSingleMethod extends DeclareSingleMethod {
		@Override
		public void method() {
		}
	}

	/**
	 * Tests if methods will be overridden by methods further down in the class
	 * hierarchy.
	 */
	@Test
	public void testMethodOverride() {
		ClassIR ir = parser.parseClass(ExtendDeclareSingleMethod.class, null);
		checkMethod(ir, ExtendDeclareSingleMethod.class, "method");
	}

	@Retention(RetentionPolicy.RUNTIME)
	public static @interface ParamAnnotation {
	}

	public static class DeclareParamMethod {
		@IRClass(MethodIR.class)
		public void method(@ParamAnnotation Object param, Object withoutParam) {
		}
	}

	/**
	 * Tests if parameter annotations are collected from the parameters.
	 */
	@Test
	public void testParamAnnotationCollect() {
		ClassIR ir = parser.parseClass(DeclareParamMethod.class, null);
		MethodIR mir = (MethodIR) ir.memberIRs.get(Method.class).iterator()
				.next();
		Assert.assertEquals(2, mir.getParameterAnnotations().length);
		Assert.assertEquals(1, mir.getParameterAnnotations()[0].size());
		Assert.assertNotNull(mir.getParameterAnnotations()[0]
				.get(ParamAnnotation.class));
		Assert.assertTrue(mir.getParameterAnnotations()[1].isEmpty());
	}

	public static class DeclareTwoParamsMethod {
		@IRClass(MethodIR.class)
		public void method(@ParamAnnotation Object param,
				@ParamAnnotation Object param2) {
		}
	}

	public static class ExtendAndOverrideOneParam extends
			DeclareTwoParamsMethod {
		@Override
		public void method(Object param, @AnnotationOverride Object param2) {
		}
	}

	/**
	 * Tests if parameter annotations are kept on method override and removed if
	 * <tt>AnnotationOverride</tt> is encountered.
	 */
	@Test
	public void testParamAnnotationOverride() {
		ClassIR ir = parser.parseClass(ExtendAndOverrideOneParam.class, null);
		MethodIR mir = (MethodIR) ir.memberIRs.get(Method.class).iterator()
				.next();
		Assert.assertEquals(2, mir.getParameterAnnotations().length);
		Assert.assertEquals(1, mir.getParameterAnnotations()[0].size());
		Assert.assertNotNull(mir.getParameterAnnotations()[0]
				.get(ParamAnnotation.class));
		Assert.assertTrue(mir.getParameterAnnotations()[1].isEmpty());
	}

}
